﻿using System;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using System.Text;

namespace O1.Kernel.Primitives
{
    [ContractVerification(true)]
    public struct StringProperty : IEquatable<StringProperty>
    {
        public const int FixedSize = Sz.Cell.PropStringKeyAndSize;

        public readonly string Value;

        public readonly int Key;

        private const int MaxStringBytes = 15;

        private StringProperty(int key, string value)
            : this()
        {
            this.Key = key;
            this.Value = value;
        }

        public static StringProperty Create(int key, string value)
        {
            // Fine for ASCII, not great for exotic 6 byte encodings:
            Contract.Requires<ArgumentException>(Encoding.UTF8.GetByteCount(value ?? string.Empty) <= MaxStringBytes, ExceptionText.StringValueMustBeSmall);

            return new StringProperty(key, value);
        }

        public static bool operator ==(StringProperty instance, StringProperty other)
        {
            return instance.Equals(other);
        }

        public static bool operator !=(StringProperty instance, StringProperty other)
        {
            return !instance.Equals(other);
        }

        public bool Equals(StringProperty other)
        {
            return this.Key == other.Key && this.Value == other.Value;
        }

        public override int GetHashCode()
        {
            return this.Key.GetHashCode() ^ (this.Value ?? string.Empty).GetHashCode();
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }

            if (obj.GetType() != typeof(StringProperty))
            {
                return false;
            }

            return this.Equals((StringProperty)obj);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Requires encoding calculation which is more expensive than a typical property access.")]
        public int GetSize()
        {
            return string.IsNullOrEmpty(this.Value) ? 
                FixedSize : 
                FixedSize + Encoding.UTF8.GetByteCount(this.Value);
        }

        internal static int GetSize(byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + FixedSize <= buffer.Length);

            return FixedSize + buffer[offset + FixedSize - 1];
        }

        internal static unsafe StringProperty Read(byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + FixedSize < buffer.Length);

            fixed (byte* p = &buffer[offset])
            {
                var size = *((byte*)(p + Sz.CLR.Int32));
                return new StringProperty(
                    *((int*)p),
                    Encoding.UTF8.GetString(buffer, offset + FixedSize, size));
            }
        }

        internal unsafe void Write(byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + FixedSize < buffer.Length);

            var value = this.Value;
            var size = this.Value == null ? 0 : Encoding.UTF8.GetByteCount(this.Value);
            fixed (byte* p = &buffer[offset])
            {
                *((int*)p) = this.Key;
                *(p + Sz.CLR.Int32) = (byte)size;

                if (size > 0)
                {
                    fixed (char* c = value)
                    {
                        Encoding.UTF8.GetBytes(c, size, p + Sz.Cell.PropStringKeyAndSize, size);
                    }
                }
            }
        }
    }
}